package com.summer.filesync.sync;

import com.summer.filesync.config.ConfigManager;
import com.summer.filesync.config.SystemConfig;
import com.summer.filesync.util.FileUtils;
import com.summer.filesync.util.Logger;
import com.summer.filesync.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;

/** 文件监控器 监控指定目录的文件变化并生成事件 */
public class FileWatcher implements Runnable {

  private static final Logger logger = Logger.getLogger(FileWatcher.class);

  private WatchService watchService;
  private Map<WatchKey, Path> watchKeys;
  private Set<String> monitoredPaths;
  private BlockingQueue<FileEvent> eventQueue;
  private boolean isRunning = false;
  private String nodeId;

  // 文件状态缓存，用于检测真正的变化
  private Map<String, FileInfo> fileInfoCache;

  public FileWatcher() throws IOException {
    this.watchService = FileSystems.getDefault().newWatchService();
    this.watchKeys = new ConcurrentHashMap<>();
    this.monitoredPaths = ConcurrentHashMap.newKeySet();
    this.eventQueue = new LinkedBlockingQueue<>();
    this.fileInfoCache = new ConcurrentHashMap<>();

    ConfigManager config = ConfigManager.getInstance();
    this.nodeId = config.getProperty(SystemConfig.NODE_ID, "unknown");

    // 初始化监控目录
    initializeWatchPaths();
  }

  /** 初始化监控路径 */
  private void initializeWatchPaths() {
    ConfigManager config = ConfigManager.getInstance();
    String watchDirectories =
        config.getProperty(SystemConfig.WATCH_DIRECTORIES, SystemConfig.Defaults.WATCH_DIRECTORIES);

    if (StringUtils.isNotEmpty(watchDirectories)) {
      String[] paths = watchDirectories.split(",");
      for (String path : paths) {
        try {
          addWatchPath(path.trim());
        } catch (IOException e) {
          logger.error("Failed to add watch path: " + path, e);
        }
      }
    }
  }

  /** 添加监控目录 */
  public void addWatchPath(String path) throws IOException {
    if (StringUtils.isEmpty(path)) {
      throw new IllegalArgumentException("Watch path cannot be empty");
    }

    Path watchPath = Paths.get(path).toAbsolutePath();

    // 创建目录（如果不存在）
    if (!Files.exists(watchPath)) {
      Files.createDirectories(watchPath);
      logger.info("Created watch directory: " + watchPath);
    }

    if (!Files.isDirectory(watchPath)) {
      throw new IOException("Watch path must be a directory: " + watchPath);
    }

    // 注册监控
    WatchKey key =
        watchPath.register(
            watchService,
            StandardWatchEventKinds.ENTRY_CREATE,
            StandardWatchEventKinds.ENTRY_MODIFY,
            StandardWatchEventKinds.ENTRY_DELETE);

    watchKeys.put(key, watchPath);
    monitoredPaths.add(watchPath.toString());

    logger.info("Added watch path: " + watchPath);

    // 递归添加子目录
    addSubDirectories(watchPath);

    // 初始化文件信息缓存
    initializeFileCache(watchPath);
  }

  /** 递归添加子目录到监控 */
  private void addSubDirectories(Path rootPath) throws IOException {
    Files.walk(rootPath)
        .filter(Files::isDirectory)
        .filter(path -> !path.equals(rootPath))
        .forEach(
            subDir -> {
              try {
                WatchKey key =
                    subDir.register(
                        watchService,
                        StandardWatchEventKinds.ENTRY_CREATE,
                        StandardWatchEventKinds.ENTRY_MODIFY,
                        StandardWatchEventKinds.ENTRY_DELETE);
                watchKeys.put(key, subDir);
                logger.debug("Added subdirectory to watch: " + subDir);
              } catch (IOException e) {
                logger.error("Failed to add subdirectory to watch: " + subDir, e);
              }
            });
  }

  /** 初始化文件信息缓存 */
  private void initializeFileCache(Path rootPath) {
    try {
      Files.walk(rootPath)
          .filter(Files::isRegularFile)
          .forEach(
              filePath -> {
                try {
                  File file = filePath.toFile();
                  String checksum = FileUtils.calculateMD5(file);
                  FileInfo fileInfo = new FileInfo(file.length(), file.lastModified(), checksum);
                  fileInfoCache.put(filePath.toString(), fileInfo);
                } catch (Exception e) {
                  logger.error("Error initializing file cache for: " + filePath, e);
                }
              });
    } catch (IOException e) {
      logger.error("Error walking directory for cache initialization: " + rootPath, e);
    }
  }

  /** 移除监控目录 */
  public void removeWatchPath(String path) {
    Path watchPath = Paths.get(path).toAbsolutePath();
    String pathString = watchPath.toString();

    // 移除相关的WatchKey
    watchKeys
        .entrySet()
        .removeIf(
            entry -> {
              if (entry.getValue().toString().startsWith(pathString)) {
                entry.getKey().cancel();
                return true;
              }
              return false;
            });

    monitoredPaths.remove(pathString);

    // 清理文件信息缓存
    fileInfoCache.entrySet().removeIf(entry -> entry.getKey().startsWith(pathString));

    logger.info("Removed watch path: " + watchPath);
  }

  @Override
  public void run() {
    logger.info("FileWatcher started, monitoring " + monitoredPaths.size() + " paths");
    isRunning = true;

    while (isRunning) {
      try {
        // 等待文件系统事件
        WatchKey key = watchService.take();

        if (!isRunning) {
          break;
        }

        Path watchedPath = watchKeys.get(key);
        if (watchedPath == null) {
          logger.warn("Unknown watch key: " + key);
          continue;
        }

        // 处理事件
        for (WatchEvent<?> event : key.pollEvents()) {
          processWatchEvent(watchedPath, event);
        }

        // 重置key以继续监控
        boolean valid = key.reset();
        if (!valid) {
          logger.warn("Watch key is no longer valid: " + watchedPath);
          watchKeys.remove(key);
        }

      } catch (InterruptedException e) {
        logger.info("FileWatcher interrupted");
        Thread.currentThread().interrupt();
        break;
      } catch (Exception e) {
        logger.error("Error in file watcher", e);
      }
    }

    logger.info("FileWatcher stopped");
  }

  /** 处理监控事件 */
  private void processWatchEvent(Path watchedPath, WatchEvent<?> event) {
    WatchEvent.Kind<?> kind = event.kind();

    if (kind == StandardWatchEventKinds.OVERFLOW) {
      logger.warn("Watch event overflow detected");
      return;
    }

    @SuppressWarnings("unchecked")
    WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
    Path relativePath = pathEvent.context();
    Path fullPath = watchedPath.resolve(relativePath);

    try {
      FileEvent fileEvent = createFileEvent(kind, fullPath);
      if (fileEvent != null) {
        fileEvent.setNodeId(nodeId);
        eventQueue.put(fileEvent);
        logger.debug("Generated file event: " + fileEvent.getDescription());
      }
    } catch (Exception e) {
      logger.error("Error processing watch event for: " + fullPath, e);
    }
  }

  /** 创建文件事件 */
  private FileEvent createFileEvent(WatchEvent.Kind<?> kind, Path fullPath) throws IOException {
    String filePath = StringUtils.normalizePath(fullPath.toString());
    File file = fullPath.toFile();
    boolean isDirectory = Files.isDirectory(fullPath);

    if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
      if (isDirectory) {
        // 新创建的目录也需要添加到监控
        try {
          WatchKey key =
              fullPath.register(
                  watchService,
                  StandardWatchEventKinds.ENTRY_CREATE,
                  StandardWatchEventKinds.ENTRY_MODIFY,
                  StandardWatchEventKinds.ENTRY_DELETE);
          watchKeys.put(key, fullPath);
          logger.debug("Added new directory to watch: " + fullPath);
        } catch (IOException e) {
          logger.error("Failed to add new directory to watch: " + fullPath, e);
        }

        return FileEvent.createFileCreated(filePath, 0, null, true);
      } else {
        String checksum = FileUtils.calculateMD5(file);
        long fileSize = file.length();

        // 更新缓存
        fileInfoCache.put(filePath, new FileInfo(fileSize, file.lastModified(), checksum));

        return FileEvent.createFileCreated(filePath, fileSize, checksum, false);
      }

    } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
      if (isDirectory) {
        // 目录修改通常不需要特殊处理
        return null;
      } else {
        // 检查文件是否真正改变
        FileInfo oldInfo = fileInfoCache.get(filePath);
        String newChecksum = FileUtils.calculateMD5(file);
        long newSize = file.length();
        long newModified = file.lastModified();

        if (oldInfo == null || !oldInfo.checksum.equals(newChecksum)) {
          // 文件确实发生了变化
          fileInfoCache.put(filePath, new FileInfo(newSize, newModified, newChecksum));
          return FileEvent.createFileModified(filePath, newSize, newChecksum);
        }

        // 文件没有实质变化，忽略此事件
        return null;
      }

    } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
      // 清理缓存
      fileInfoCache.remove(filePath);

      // 如果是目录，清理相关的监控和缓存
      if (isDirectory) {
        removeWatchPath(filePath);
      }

      return FileEvent.createFileDeleted(filePath, isDirectory);
    }

    return null;
  }

  /** 获取事件队列 */
  public BlockingQueue<FileEvent> getEventQueue() {
    return eventQueue;
  }

  /** 停止监控 */
  public void stop() {
    isRunning = false;

    try {
      if (watchService != null) {
        watchService.close();
      }
    } catch (IOException e) {
      logger.error("Error closing watch service", e);
    }

    logger.info("FileWatcher stop requested");
  }

  /** 检查是否正在运行 */
  public boolean isRunning() {
    return isRunning;
  }

  /** 获取监控路径列表 */
  public Set<String> getMonitoredPaths() {
    return Set.copyOf(monitoredPaths);
  }

  /** 获取队列大小 */
  public int getEventQueueSize() {
    return eventQueue.size();
  }

  /** 文件信息缓存类 */
  private static class FileInfo {
    final long size;
    final long lastModified;
    final String checksum;

    FileInfo(long size, long lastModified, String checksum) {
      this.size = size;
      this.lastModified = lastModified;
      this.checksum = checksum;
    }
  }
}
